#ifndef _NE_SHARED_PTR_H_
#define _NE_SHARED_PTR_H_

#include <neinit.h>

namespace ne
{
    template<typename T> class SharedPtr
    {
        public:
            SharedPtr()
                : mpPtr(0), mpUseCount(0)
            {
            }

            template<typename Y>
            explicit SharedPtr(Y* p)
                : mpPtr(p),
                  mpUseCount(p ? new uint32_t(1) : 0)
            {
            }

            //TODO: Copy construction is needed
            SharedPtr(const SharedPtr &p)
                : mpPtr(0), mpUseCount(0)
            {
                mpPtr = p.mpPtr;
                mpUseCount = p.mpUseCount;
                if (mpUseCount)
                {
                    ++(*mpUseCount);
                }
            }

            SharedPtr& operator = (const SharedPtr &p)
            {
                if (mpPtr == p.mpPtr)
                {
                    return *this;
                }
                SharedPtr<T> temp(p);
                swap(temp);
                return *this;
            }

            template<typename Y>
            SharedPtr(const SharedPtr<Y> &p)
                : mpPtr(0), mpUseCount(0)
            {
                mpPtr = p.ptr();
                mpUseCount = p.getUseCountPtr();
                if (mpUseCount)
                {
                    ++(*mpUseCount);
                }
            }

            template<typename Y>
            SharedPtr& operator = (const SharedPtr<Y> &p)
            {
                if (mpPtr == p.ptr())
                {
                    return *this;
                }
                SharedPtr<T> temp(p);
                swap(temp);
                return *this;
            }

            virtual ~SharedPtr()
            {
                release();
            }

            inline T& operator * () const
            {
                assert(mpPtr);
                return *mpPtr;
            }

            inline T* operator -> () const
            {
                assert(mpPtr);
                return mpPtr;
            }

            inline T* ptr() const
            {
                return mpPtr;
            }

            inline bool isNull() const
            {
                return mpPtr == 0;
            }

            inline bool notNull() const
            {
                return mpPtr != 0;
            }

            inline void setNull()
            {
                if (mpPtr)
                {
                    release();
                    mpPtr = 0;
                    mpUseCount = 0;
                }
            }

            inline bool unique() const
            {
                assert(mpUseCount);
                return *mpUseCount == 1;
            }

            inline uint32_t getUseCount() const
            {
                assert(mpUseCount);
                return *mpUseCount;
            }

            inline uint32_t* getUseCountPtr() const
            {
                return mpUseCount;
            }

            void bind(T *p)
            {
                assert(!mpPtr && !mpUseCount);
                mpPtr = p;
                mpUseCount = new uint32_t(1);
            }

            virtual SharedPtr& swap(SharedPtr &p)
            {
                std::swap(mpPtr, p.mpPtr);
                std::swap(mpUseCount, p.mpUseCount);
                return *this;
            }

        protected:
            void release()
            {
                bool canDestroy = false;
                if (mpUseCount)
                {
                    if (--(*mpUseCount) == 0)
                    {
                        canDestroy = true;
                    }
                }
                if (canDestroy)
                {
                    destroy();
                }
            }

            virtual void destroy()
            {
                delete mpPtr;
                mpPtr = 0;
                delete mpUseCount;
                mpUseCount = 0;
            }

        protected:
            T *mpPtr;
            uint32_t *mpUseCount;
    };

    template<typename T, typename Y>
    inline bool operator == (const SharedPtr<T> &a, const SharedPtr<Y> &b)
    {
        return a.ptr() == b.ptr();
    }

    template<typename T, typename Y>
    inline bool operator != (const SharedPtr<T> &a, const SharedPtr<Y> &b)
    {
        return a.ptr() != b.ptr();
    }

    template<typename T, typename Y>
    inline bool operator < (const SharedPtr<T> &a, const SharedPtr<Y> &b)
    {
        return std::less<const void*>()(a.ptr(), b.ptr());
    }

    template<typename T, typename Y>
    inline bool operator > (const SharedPtr<T> &a, const SharedPtr<Y> &b)
    {
        return std::less<const void*>()(b.ptr(), a.ptr());
    }
}

#endif
